-- Options.lua
-- (General tab updates: size slider 10–150%, Heals hide toggle, Heals "+" toggle, Text strata dropdown, Skill icons toggle)
local ADDON, DFFT = ...
if not DFFT then return end

-- === LIVE DB PROXY ===
local function __getDB() if type(DFFT.DB) ~= "table" then DFFT.DB = {} end return DFFT.DB end
local DB = setmetatable({}, {
  __index    = function(_, k) local d = __getDB(); return d and d[k] end,
  __newindex = function(_, k, v) local d = __getDB(); if d then d[k] = v end end,
  __pairs    = function(_) local d = __getDB(); return pairs(d) end,
})

-- ===== Safe defaults =====
DB.healer = DB.healer or {}
DB.healer.absorbs = DB.healer.absorbs or {}
if DB.healer.absorbs.show == nil then DB.healer.absorbs.show = false end
if DB.healer.showAbsorbs == nil then DB.healer.showAbsorbs = DB.healer.absorbs.show end
if DB.showAbsorbs       == nil then DB.showAbsorbs       = DB.healer.absorbs.show end
if DB.healer.absorbsEnabled == nil then DB.healer.absorbsEnabled = DB.healer.absorbs.show end

-- NEW defaults used by the new UI
if DB.bigCritShadowEnabled == nil then DB.bigCritShadowEnabled = true end
if DB.textSizePercent      == nil then DB.textSizePercent      = 100  end  -- 10–150 slider (percent)
if DB.healPlusEnabled      == nil then DB.healPlusEnabled      = true end  -- "+" on heals ON by default
if DB.showIcons            == nil then DB.showIcons            = false end -- skill icons OFF by default
if DB.textStrata           == nil then DB.textStrata           = "HIGH" end
-- Hide-all-heals: accept legacy location, then mirror to both places
if DB.hideHeals == nil and DB.healer and DB.healer.hideHeals ~= nil then DB.hideHeals = not not DB.healer.hideHeals end
if DB.hideHeals == nil then DB.hideHeals = false end
DB.healer.hideHeals = not not DB.hideHeals

-- NEW: Hide overheal (default OFF = show overheal)
if DB.hideOverheal == nil then DB.hideOverheal = false end
DB.healer.hideOverheal = not not DB.hideOverheal
if C_Timer and C_Timer.After then
  C_Timer.After(0, function()
    if DFFT and DFFT.Healer then
      if type(DFFT.Healer.SetHealsHidden)=="function" then pcall(DFFT.Healer.SetHealsHidden, not not DB.hideHeals) end
      if type(DFFT.Healer.SetHideOverheal)=="function" then pcall(DFFT.Healer.SetHideOverheal, not not DB.hideOverheal) end
    end
  end)
end

-- Friendly name from .toc (fallback)
local FRIENDLY_NAME = "流畅战斗文本"
if GetAddOnMetadata then
  local title = GetAddOnMetadata(ADDON, "Title")
  if type(title) == "string" and title ~= "" then FRIENDLY_NAME = title end
end

-- =============== utils ===============
local function deepcopy(src)
  if type(src) ~= "table" then return src end
  local t = {}
  for k,v in pairs(src) do t[k]=deepcopy(v) end
  return t
end

local function clamp(x,a,b) if x<a then return a elseif x>b then return b end return x end
local function mix(a,b,t) return a + (b-a)*t end

-- UI is pixel-based; ~96 dpi -> 37.8 px per cm (approx, good enough for positioning)
local function cm(n) return math.floor((n or 0) * 37.7953 + 0.5) end

local function hex(h)
  h = (h or ""):gsub("#","")
  local r = tonumber(h:sub(1,2),16) or 255
  local g = tonumber(h:sub(3,4),16) or 255
  local b = tonumber(h:sub(5,6),16) or 255
  return {r/255, g/255, b/255}
end
local function rgb(c) return (c and c[1] or 1), (c and c[2] or 1), (c and c[3] or 1) end
local function shade(c, mul) return {clamp((c[1] or 1)*mul,0,1), clamp((c[2] or 1)*mul,0,1), clamp((c[3] or 1)*mul,0,1)} end
local function saturateTowards(c, tgt, amt)
  local t = clamp(amt or 0.25, 0, 1)
  return { mix(c[1], tgt[1], t), mix(c[2], tgt[2], t), mix(c[3], tgt[3], t) }
end

-- ===== ColorPicker compat =====
local function ShowColorPickerCompat(initR, initG, initB, onColor, onCancel)
  local function callOnColor(r,g,b) if type(onColor)=="function" then pcall(onColor, r,g,b) end end
  local function callOnCancel(r,g,b) if type(onCancel)=="function" then pcall(onCancel, r,g,b) end end
  local cpf = _G.ColorPickerFrame
  if cpf and cpf.SetupColorPickerAndShow then
    local info = {
      swatchFunc = function() local r,g,b = cpf:GetColorRGB(); callOnColor(r,g,b) end,
      cancelFunc = function(prev)
        local r, g, b
        if type(prev)=="table" then
          r, g, b = prev.r or prev[1] or initR, prev.g or prev[2] or initG, prev.b or prev[3] or initB
        else
          r, g, b = initR, initG, initB
        end
        callOnCancel(r,g,b)
      end,
      hasOpacity = false,
      r = initR, g = initG, b = initB,
    }
    cpf:SetupColorPickerAndShow(info); return
  end
  if cpf then
    cpf:Hide(); cpf.hasOpacity = false; cpf.opacity = 0
    cpf.previousValues = {initR, initG, initB}
    cpf.func = function() local r,g,b = cpf:GetColorRGB(); callOnColor(r,g,b) end
    cpf.cancelFunc = function(prev)
      local p = prev or cpf.previousValues or {initR,initG,initB}
      callOnCancel(p[1] or initR, p[2] or initG, p[3] or initB)
    end
    if cpf.SetColorRGB then cpf:SetColorRGB(initR, initG, initB) end
    cpf:Show(); return
  end
  callOnColor(initR,initG,initB)
end

-- notify engine on config change
local function NotifyConfigChanged()
  -- keep mirrors
  DB.healer = DB.healer or {}
  DB.healer.hideHeals     = not not DB.hideHeals
  DB.healer.hideOverheal  = not not DB.hideOverheal

  if DFFT then
    if DFFT.Healer then
      if type(DFFT.Healer.RefreshPalette)=="function" then pcall(DFFT.Healer.RefreshPalette) end
      if type(DFFT.Healer.RebuildColors)=="function" then pcall(DFFT.Healer.RebuildColors) end
      if type(DFFT.Healer.ApplyTheme)=="function" then pcall(DFFT.Healer.ApplyTheme, DB.healTheme) end
      if type(DFFT.Healer.SetPlusEnabled)=="function" then pcall(DFFT.Healer.SetPlusEnabled, not not DB.healPlusEnabled) end
      -- propagate hide toggles if module exposes them
      if type(DFFT.Healer.SetHealsHidden)=="function" then pcall(DFFT.Healer.SetHealsHidden, not not DB.hideHeals) end
      if type(DFFT.Healer.SetHideOverheal)=="function" then pcall(DFFT.Healer.SetHideOverheal, not not DB.hideOverheal) end
    end
    if type(DFFT.refreshActiveFonts)=="function" then pcall(DFFT.refreshActiveFonts) end
    if type(DFFT.RefreshPalette)=="function" then pcall(DFFT.RefreshPalette) end
    if type(DFFT.RebuildColors)=="function" then pcall(DFFT.RebuildColors) end
    if type(DFFT.OnConfigChanged)=="function" then pcall(DFFT.OnConfigChanged, "colors") end
    if type(DFFT.SetBigCritShadowEnabled)=="function" then pcall(DFFT.SetBigCritShadowEnabled, not not DB.bigCritShadowEnabled) end
    if type(DFFT.OnConfigChanged)=="function" then pcall(DFFT.OnConfigChanged, "general") end
  end
end

-- =============== Profiles: delegated to Profiles.lua ===============
do
  DFFT.Profile = DFFT.Profile or {}
  DFFT.Profile.List      = DFFT.Profile.List      or function() return {} end
  DFFT.Profile.Exists    = DFFT.Profile.Exists    or function() return false end
  DFFT.Profile.Save      = DFFT.Profile.Save      or function() return false, "Profiles.lua not available" end
  DFFT.Profile.Load      = DFFT.Profile.Load      or function() return false, "Profiles.lua not available" end
  DFFT.Profile.Delete    = DFFT.Profile.Delete    or function() return false, "Profiles.lua not available" end
  DFFT.Profile.Rename    = DFFT.Profile.Rename    or function() return false, "Profiles.lua not available" end
  DFFT.Profile.SetActiveForChar = DFFT.Profile.SetActiveForChar or function() return false, "Profiles.lua not available" end
  DFFT.Profile.GetActiveForChar = DFFT.Profile.GetActiveForChar or function() return nil end
  if not DFFT.Profile.SetActiveAndLoadForChar then
    DFFT.Profile.SetActiveAndLoadForChar = function(name)
      local ok, err = DFFT.Profile.SetActiveForChar(name)
      if not ok then return ok, err end
      return DFFT.Profile.Load(name, false)
    end
  end
end

-- =============== UI helpers ===============
local function createLabel(parent, text, size)
  local fs = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
  if size == "title" then fs:SetFontObject("GameFontNormalLarge") end
  fs:SetText(text or ""); fs:SetJustifyH("LEFT"); fs:SetJustifyV("MIDDLE")
  return fs
end

local function createCheckbox(parent, label, tooltip, get, set)
  local cb = CreateFrame("CheckButton", nil, parent, "ChatConfigCheckButtonTemplate")
  cb.tooltip = tooltip
  if cb.Text then cb.Text:SetText(label) end
  cb:SetScript("OnClick", function(self)
    pcall(set, self:GetChecked())
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
    NotifyConfigChanged()
  end)
  cb:SetScript("OnEnter", function(self)
    local tip = self.tooltip
    if not tip or tip == "" then return end
    GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
    GameTooltip:SetText(tip, nil, nil, nil, true)
    GameTooltip:Show()
  end)
  cb:SetScript("OnLeave", GameTooltip_Hide)
  local ok, val = pcall(get)
  cb:SetChecked(ok and val and true or false)
  return cb
end

local function createSlider(parent, label, minV, maxV, step, get, set, fmt, width)
  local s = CreateFrame("Slider", nil, parent, "OptionsSliderTemplate")
  s:SetMinMaxValues(minV, maxV); s:SetValueStep(step); s:SetObeyStepOnDrag(true)
  s:SetWidth(width or 320)
  if s.Low  then s.Low:SetText(tostring(minV)) end
  if s.High then s.High:SetText(tostring(maxV)) end
  local text = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
  text:SetPoint("BOTTOMLEFT", s, "TOPLEFT", 0, 6); text:SetText(label)
  s.valFS = s:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
  s.valFS:SetPoint("TOP", s, "BOTTOM", 0, -2)
  local function round(v)
    if step and step>0 then
      local n = math.floor(v/step + 0.5)
      return tonumber(string.format("%.6f", n*step))
    end
    return v
  end
  local function show(v) if fmt then s.valFS:SetText(fmt(v)) else s.valFS:SetText(tostring(v)) end end
  local ok, cur = pcall(get); if not ok or type(cur)~="number" then cur = minV; pcall(set, cur) end
  s:SetScript("OnValueChanged", function(self, val)
    if type(val)~="number" then return end
    val = round(val); pcall(set, val); show(val); NotifyConfigChanged()
  end)
  s:SetValue(cur); show(cur); s.label = text
  return s
end

local function resolveFontDisplay(v)
  if not v then return "" end
  if type(DFFT.KNOWN_FONTS) == "table" then
    if DFFT.KNOWN_FONTS[v] ~= nil then return v end
    for k, val in pairs(DFFT.KNOWN_FONTS) do
      if val == v then return k end
      if type(val) == "table" then
        if val.path == v or val.file == v or val.name == v then return k end
      end
    end
  end
  if type(v) == "string" then
    local base = v:match("([^/\\]+)$") or v
    base = base:gsub("%.ttf$", ""):gsub("%.otf$", ""):gsub("_"," "):gsub("-"," ")
    base = base:gsub("%s+"," "):gsub("^%l", string.upper)
    return base
  end
  return tostring(v)
end

local function dropdown(parent, width, items, get, set)
  local f = CreateFrame("Frame", nil, parent, "UIDropDownMenuTemplate")
  f:SetPoint("TOPLEFT"); UIDropDownMenu_SetWidth(f, width or 180)
  local function currentRaw() local ok,cur = pcall(get); if ok then return cur end return items[1] end
  local function currentDisplay() return resolveFontDisplay(currentRaw()) end
  local function ensureInList(name)
    for _,n in ipairs(items) do if n==name then return end end
    table.insert(items, 1, name)
  end
  ensureInList(currentDisplay())
  UIDropDownMenu_Initialize(f, function(self, level)
    local cur = currentDisplay()
    for _, name in ipairs(items) do
      local info = UIDropDownMenu_CreateInfo()
      info.text = name; info.arg1 = name; info.checked = (name == cur)
      info.func = function(_, arg1)
        pcall(set, arg1); UIDropDownMenu_SetSelectedName(f, arg1); UIDropDownMenu_SetText(f, arg1)
        CloseDropDownMenus(); UIDropDownMenu_Refresh(f); NotifyConfigChanged()
      end
      UIDropDownMenu_AddButton(info, level)
    end
  end)
  local init = currentDisplay(); UIDropDownMenu_SetSelectedName(f, init); UIDropDownMenu_SetText(f, init)
  f._refresh = function()
    local n = currentDisplay(); ensureInList(n)
    UIDropDownMenu_SetSelectedName(f, n); UIDropDownMenu_SetText(f, n); UIDropDownMenu_Refresh(f)
  end
  f:HookScript("OnShow", f._refresh)
  if C_Timer and C_Timer.After then C_Timer.After(0, f._refresh) end
  return f
end

-- Scrollable area helper
local function createScrollArea(parent)
  local scroll = CreateFrame("ScrollFrame", nil, parent, "UIPanelScrollFrameTemplate")
  scroll:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, -10)
  scroll:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", -24, 0)
  local content = CreateFrame("Frame", nil, scroll); content:SetSize(1,1); scroll:SetScrollChild(content)
  if scroll.ScrollBar then
    scroll.ScrollBar:Hide()
    local rgns = { scroll.ScrollBar:GetRegions() }
    for _, r in ipairs(rgns) do if r and r.Hide then r:Hide() end end
  end
  scroll:EnableMouseWheel(true)
  scroll:SetScript("OnMouseWheel", function(self, delta)
    local cur = self:GetVerticalScroll() or 0
    local max = self:GetVerticalScrollRange() or 0
    local step = 40; cur = cur - (delta * step)
    if cur < 0 then cur = 0 end
    if cur > max then cur = max end
    self:SetVerticalScroll(cur)
  end)
  return content
end

local function addInfoIcon(parent, anchorTo, textTitle, textBody)
  local info = CreateFrame("Frame", nil, parent)
  info:SetSize(16, 16); info:SetPoint("LEFT", anchorTo, "RIGHT", 6, 0); info:EnableMouse(true)
  local tex = info:CreateTexture(nil, "ARTWORK")
  tex:SetAllPoints(); tex:SetTexture("Interface\\FriendsFrame\\InformationIcon")
  info:SetScript("OnEnter", function(self)
    GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
    if textTitle and textTitle ~= "" then GameTooltip:SetText(textTitle, 1, 1, 1) end
    if textBody and textBody ~= "" then GameTooltip:AddLine(textBody, nil, nil, nil, true) end
    GameTooltip:Show()
  end)
  info:SetScript("OnLeave", GameTooltip_Hide)
  return info
end

-- ================== Local themes (fallbacks) ==================
local LOCAL_DAMAGE_THEMES = {
  ["Pastel"] = { small=hex("#c5c5c5"), medium=hex("#f4e8cb"), big=hex("#f3cbda"), ultra=hex("#ecb9b8"), nt=hex("#aedcd8") },
  ["Basic"]  = { small={0.70,0.70,0.70}, medium={1.00,1.00,1.00}, big={1.00,0.85,0.00}, ultra={1.00,0.55,0.00}, nt={0.45,0.75,1.00} },
  ["Warcraft"] = { small=hex("#fff3a6"), medium=hex("#f8de00"), big=hex("#ffc700"), ultra=hex("#ffab00"), nt=hex("#2A8FFF") },
  ["Sanguin"] = { small=hex("#8b4a4a"), medium=hex("#b04343"), big=hex("#cf2626"), ultra=hex("#8f0000"), nt=hex("#5aa2cf") },
  ["Arcade Néon"] = { small=hex("#00ffb7"), medium=hex("#00ffff"), big=hex("#ff00ff"), ultra=hex("#ffff00"), nt=hex("#1ec8ff") },
  ["Glacial"] = { small=hex("#9fd5ff"), medium=hex("#65b5ff"), big=hex("#2f7fff"), ultra=hex("#124fd8"), nt=hex("#85e6e6") },
  ["Embers"] = { small=hex("#e0c9a9"), medium=hex("#f1b06a"), big=hex("#ff7b2b"), ultra=hex("#ff3a0a"), nt=hex("#73c3ff") },
  ["Toxic"] = { small=hex("#c7ff8a"), medium=hex("#9fff3d"), big=hex("#66d815"), ultra=hex("#33b104"), nt=hex("#7de0d8") },
  ["Golden"] = { small=hex("#aeaeae"), medium=hex("#dedede"), big=hex("#ffd36b"), ultra=hex("#ffad2e"), nt=hex("#93c8ff") },
}
LOCAL_DAMAGE_THEMES["Monochrome+Or"] = LOCAL_DAMAGE_THEMES["Golden"]

local LOCAL_HEAL_THEMES = {
  ["Mint"]         = { heal = hex("#98f5cc"), absorb = hex("#cbeee0") },
  ["Emerald"]      = { heal = hex("#2ecc71"), absorb = hex("#a8f2c9") },
  ["Aquatic"]      = { heal = hex("#4dd0e1"), absorb = hex("#b3e5fc") },
  ["Golden Remedy"]= { heal = hex("#ffe680"), absorb = hex("#ffd54f") },
  ["Cotton"]       = { heal = hex("#ffffff"), absorb = hex("#d9d9d9") },
  ["Lavender"]     = { heal = hex("#c4b5fd"), absorb = hex("#e9d5ff") },
  ["Cleric"]       = { heal = hex("#a7f3d0"), absorb = hex("#fef08a") },
  ["Teal & Amber"] = { heal = hex("#14b8a6"), absorb = hex("#f59e0b") },
}

-- ================== Apply helpers ==================
local function ApplyDamageThemeLocal(name)
  local t = LOCAL_DAMAGE_THEMES[name or ""]; if not t then return end
  DB.smallColor     = { t.small[1], t.small[2], t.small[3] }
  DB.mediumColor    = { t.medium[1], t.medium[2], t.medium[3] }
  DB.bigColor       = { t.big[1], t.big[2], t.big[3] }
  DB.veryBigColor   = { t.ultra[1], t.ultra[2], t.ultra[3] }
  DB.nonTargetColor = { t.nt[1], t.nt[2], t.nt[3] }
  DB.damageTheme    = name
  print("|cff88ff88SCT|r Damage theme applied:", name)
end

local function ApplyHealThemeLocal(name)
  local t = LOCAL_HEAL_THEMES[name or ""]; if not t then return end
  DB.healer = DB.healer or {}; DB.healer.colors = DB.healer.colors or {}
  local base = { t.heal[1], t.heal[2], t.heal[3] }
  DB.healer.colors.target = {
    small  = shade(base, 1.10), medium = shade(base, 1.00), big = shade(base, 0.90), vbig = shade(base, 0.80),
  }
  DB.healer.colors.nontarget = {
    small  = shade(base, 1.10), medium = shade(base, 1.00), big = shade(base, 0.90), vbig = shade(base, 0.80),
  }
  DB.healer.colors.absorb = { t.absorb[1], t.absorb[2], t.absorb[3] }

  DB.healer.healSmallColor     = deepcopy(DB.healer.colors.target.small)
  DB.healer.healMediumColor    = deepcopy(DB.healer.colors.target.medium)
  DB.healer.healBigColor       = deepcopy(DB.healer.colors.target.big)
  DB.healer.healVeryBigColor   = deepcopy(DB.healer.colors.target.vbig)
  DB.healer.nonTargetHealSmallColor   = deepcopy(DB.healer.colors.nontarget.small)
  DB.healer.nonTargetHealMediumColor  = deepcopy(DB.healer.colors.nontarget.medium)
  DB.healer.nonTargetHealBigColor     = deepcopy(DB.healer.colors.nontarget.big)
  DB.healer.nonTargetHealVeryBigColor = deepcopy(DB.healer.colors.nontarget.vbig)
  DB.healer.absorbColor   = deepcopy(DB.healer.colors.absorb)
  DB.healer.absorbs       = DB.healer.absorbs or {}
  DB.healer.absorbs.color = deepcopy(DB.healer.colors.absorb)

  DB.healTheme = name
  print("|cff88ff88SCT|r Healer theme applied:", name)
end

local function mapDamageDisplayToReal(name)
  if name == "Basic" and DFFT.DamageThemes and DFFT.DamageThemes["Blizzard UI"] and not DFFT.DamageThemes["Basic"] then
    return "Blizzard UI"
  end
  if name == "Golden" then
    if DFFT.DamageThemes and DFFT.DamageThemes["Golden"] then return "Golden" end
    if DFFT.DamageThemes and DFFT.DamageThemes["Monochrome+Or"] then return "Monochrome+Or" end
  end
  return name
end

local function ApplyDamageTheme(name)
  local real = mapDamageDisplayToReal(name)
  if DFFT and type(DFFT.ApplyDamageTheme) == "function" then
    pcall(DFFT.ApplyDamageTheme, real); DB.damageTheme = name
  else
    ApplyDamageThemeLocal(name)
  end
  NotifyConfigChanged()
end

local function ApplyHealTheme(name)
  if DFFT and type(DFFT.ApplyHealTheme) == "function" then
    pcall(DFFT.ApplyHealTheme, name); DB.healTheme = name
  else
    ApplyHealThemeLocal(name)
  end
  NotifyConfigChanged()
end

-- helper: propagate alpha to many possible keys for compatibility
local function SetBigAlpha(v)  DB.bigAlpha = v; DB.bigTextAlpha = v; DB.critAlpha = v end
local function SetUltraAlpha(v) DB.veryBigAlpha = v; DB.ultraAlpha = v end
local function SetNonTargetAlpha(v)
  DB.nonTargetAlpha = v
  DB.nonTargetSmallAlphaCap  = v
  DB.nonTargetMediumAlphaCap = v
  DB.nonTargetBigAlphaCap    = v
  DB.nonTargetVeryBigAlphaCap= v
end

-- Default opacity values used by Colors tab
local DEFAULT_SMALL_ALPHA   = 0.90
local DEFAULT_MEDIUM_ALPHA  = 1.00
local DEFAULT_BIG_ALPHA     = 1.00
local DEFAULT_ULTRA_ALPHA   = 1.00
local DEFAULT_NONTARGET_CAP = 1.00

-- ===== Text size % apply (10–150) =====
local function _ensureBaseSizeSnapshot()
  -- Snap once from defaults if available, else from current DB
  if DB._sizeBaseSmall and DB._sizeBaseMedium and DB._sizeBaseBigBase and DB._sizeBaseBigMax then return end
  local defs = DFFT.defaults or {}
  DB._sizeBaseSmall   = defs.smallScale   or DB.smallScale    or 0.90
  DB._sizeBaseMedium  = defs.mediumScale  or DB.mediumScale   or 1.00
  DB._sizeBaseBigBase = defs.bigScaleBase or DB.bigScaleBase  or 1.15
  DB._sizeBaseBigMax  = defs.bigScaleMax  or DB.bigScaleMax   or 1.55
end

local function ApplyTextSizePercent(pct)
  pct = clamp(tonumber(pct) or 100, 10, 150)
  _ensureBaseSizeSnapshot()
  local mul = pct / 100.0
  DB.smallScale    = DB._sizeBaseSmall   * mul
  DB.mediumScale   = DB._sizeBaseMedium  * mul
  DB.bigScaleBase  = DB._sizeBaseBigBase * mul
  DB.bigScaleMax   = DB._sizeBaseBigMax  * mul
  DB.textSizePercent = pct
end

local function ResetColorsToDefaults(refreshers)
  ApplyDamageTheme("Basic"); ApplyHealTheme("Cleric")
  DB.smallAlpha  = DEFAULT_SMALL_ALPHA
  DB.mediumAlpha = DEFAULT_MEDIUM_ALPHA
  SetBigAlpha(DEFAULT_BIG_ALPHA)
  SetUltraAlpha(DEFAULT_ULTRA_ALPHA)
  SetNonTargetAlpha(DEFAULT_NONTARGET_CAP)

  -- font back to default
  if DFFT and DFFT.defaults and DFFT.defaults.fontName then DB.fontName = DFFT.defaults.fontName end
  if type(DFFT.refreshActiveFonts)=="function" then pcall(DFFT.refreshActiveFonts) end

  -- Keep crit shadow ON
  DB.bigCritShadowEnabled = true
  if DFFT and type(DFFT.SetBigCritShadowEnabled)=="function" then pcall(DFFT.SetBigCritShadowEnabled, true) end

  -- Reset size percent to 100 and reapply
  DB._sizeBaseSmall, DB._sizeBaseMedium, DB._sizeBaseBigBase, DB._sizeBaseBigMax = nil, nil, nil, nil
  ApplyTextSizePercent(100)

  -- reset heal toggles
  DB.hideHeals = false; DB.healer.hideHeals = false
  DB.hideOverheal = false; DB.healer.hideOverheal = false

  if refreshers then for _,fn in ipairs(refreshers) do if type(fn)=="function" then pcall(fn) end end end
  print("|cffffd100DFFT:|r Colors+font reset, opacities reset, text size 100%.")
end

-- =============== tabs/pages ===============
local optionsFrame, tabs, pages, activeTab = nil, {}, {}, 1
local function setActiveTab(idx)
  activeTab = idx
  for i, b in ipairs(tabs)  do if i==idx then b:LockHighlight() else b:UnlockHighlight() end end
  for i, p in ipairs(pages) do p:SetShown(i==idx) end
end

local function createTab(parent, text, index)
  local btn = CreateFrame("Button", nil, parent, "FriendsFrameTabTemplate")
  btn:SetText(text); btn:SetID(index)
  btn:SetScript("OnClick", function(self) PlaySound(SOUNDKIT.IG_CHARACTER_INFO_TAB); setActiveTab(self:GetID()) end)
  if index == 1 then btn:SetPoint("TOPLEFT", parent, "TOPLEFT", 12, -8)
  else btn:SetPoint("LEFT", tabs[index-1], "RIGHT", -14, 0) end
  tabs[#tabs+1] = btn

  local page = CreateFrame("Frame", nil, parent)
  page:SetPoint("TOPLEFT", parent, "TOPLEFT", 12, -60)
  page:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", -12, 38)
  page:Hide()

  local content = createScrollArea(page)
  pages[index] = page
  return content
end

local function applyBackdrop(f)
  f:SetBackdrop({
    bgFile="Interface/Tooltips/UI-Tooltip-Background",
    edgeFile="Interface/Tooltips/UI-Tooltip-Border",
    tile=true, tileSize=16, edgeSize=16,
    insets={left=4,right=4,top=4,bottom=4}
  })
  f:SetBackdropColor(0,0,0,0.92)
end

-- ----------------------
-- Build tabs
-- ----------------------
local function _populateOptionsFrame(f)
  -- Window
  f:SetSize(900, 680)
  if not f._hasAnchors then f:SetPoint("CENTER"); f._hasAnchors = true end
  f:SetMovable(true); f:EnableMouse(true)
  f:RegisterForDrag("LeftButton"); f:SetScript("OnDragStart", f.StartMoving); f:SetScript("OnDragStop", f.StopMovingOrSizing)
  applyBackdrop(f)

  -- Centered title
  local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
  title:SetPoint("TOP", f, "TOP", 0, -12)
  title:SetText(FRIENDLY_NAME .. " - Options")

  local closeX = CreateFrame("Button", nil, f, "UIPanelCloseButton")
  closeX:SetPoint("TOPRIGHT", -4, -4)
  closeX:SetScript("OnClick", function() f:Hide() end)

  -- Global quick test buttons
  local btnTestHealerTop = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
  btnTestHealerTop:SetSize(120, 22); btnTestHealerTop:SetText("测试治疗")
  btnTestHealerTop:SetPoint("RIGHT", closeX, "LEFT", -6, 0)
  btnTestHealerTop:SetScript("OnClick", function() if DFFT and DFFT.Healer and DFFT.Healer.TestHeals then DFFT.Healer.TestHeals() end end)

  local btnTestDmgTop = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
  btnTestDmgTop:SetSize(120, 22); btnTestDmgTop:SetText("测试伤害")
  btnTestDmgTop:SetPoint("RIGHT", btnTestHealerTop, "LEFT", -6, 0)
  btnTestDmgTop:SetScript("OnClick", function() if SlashCmdList and SlashCmdList.DFFT then SlashCmdList.DFFT("test") end end)

  -- Tabs
  local pgGen = createTab(f, "通用", 1)
  local pgCol = createTab(f, "颜色", 2)
  local pgI   = createTab(f, "捐赠", 3)
  local pgProf= createTab(f, "配置文件", 4)

  -- ===== General =====
  do
    local header = createLabel(pgGen, "全局设置", "title")
    header:SetPoint("TOPLEFT", 4, -8)

    local y = -36

    local cbAnchorAll = createCheckbox(pgGen,
      "在目标上显示数字（否则在自身显示）",
      "将所有数字锚定到当前目标的名条。关闭可保持数字在你角色身上显示。",
      function() return DB.globalAnchorToTarget end,
      function(v) DB.globalAnchorToTarget = v end)
    cbAnchorAll:SetPoint("TOPLEFT", 4, y)
    if cbAnchorAll.Text then
      addInfoIcon(pgGen, cbAnchorAll.Text, "在目标上显示数字",
        "专注于单个目标时推荐。在AoE或频繁切换目标时，建议禁用以保持数字在你身上居中显示。")
    end
    y = y - 40

    y = y - 12
    local spreadSlider = createSlider(pgGen, "角色侧向分散（玩家锚定）", 0, 200, 2,
      function()
        if DB.playerSideSpread ~= nil then return DB.playerSideSpread end
        if DFFT.defaults and DFFT.defaults.playerSideSpread then
          return DFFT.defaults.playerSideSpread
        end
        return 0
      end,
      function(v) DB.playerSideSpread = v end,
      function(v) return string.format("%d px", v) end, 420)
    spreadSlider:SetPoint("TOPLEFT", 4, y); y = y - 64

    -- ===== Floating Combat Text Size (slider 10–150%) =====
    local sizeHeader = createLabel(pgGen, "战斗文字大小", "title")
    sizeHeader:SetPoint("TOPLEFT", 4, y); y = y - 28

    local sizeSlider = createSlider(pgGen, "文字大小", 10, 150, 1,
      function() return clamp(tonumber(DB.textSizePercent or 100), 10, 150) end,
      function(v) ApplyTextSizePercent(v) end,
      function(v) return string.format("%d%%", v) end, 420)
    sizeSlider:SetPoint("TOPLEFT", 12, y); y = y - 64

    -- Show tiers
    local cbSmall = createCheckbox(pgGen, "显示小数字", "显示小级别的数字。",
      function() if DB.showSmall == nil then return true end return DB.showSmall end,
      function(v) DB.showSmall = not not v end)
    cbSmall:SetPoint("TOPLEFT", 12, y); y = y - 28

    local cbMedium = createCheckbox(pgGen, "显示中等数字", "显示中等级别的数字。",
      function() if DB.showMedium == nil then return true end return DB.showMedium end,
      function(v) DB.showMedium = not not v end)
    cbMedium:SetPoint("TOPLEFT", 12, y); y = y - 36

    -- Absorbs + Heals controls
    local function setAbsorbsEnabled(on)
      on = not not on
      DB.healer = DB.healer or {}
      DB.healer.absorbs = DB.healer.absorbs or {}
      DB.healer.absorbs.show = on
      DB.healer.showAbsorbs    = on
      DB.showAbsorbs           = on
      DB.healer.absorbsEnabled = on
      if DFFT and DFFT.Healer and type(DFFT.Healer.SetAbsorbsEnabled) == "function" then
        pcall(DFFT.Healer.SetAbsorbsEnabled, on)
      end
      NotifyConfigChanged()
    end

    local cbAbs = createCheckbox(pgGen, "显示吸收数值", "显示治疗吸收的数值。",
      function()
        if not DB.healer or not DB.healer.absorbs then return true end
        if DB.healer.absorbs.show == nil then return true end
        return DB.healer.absorbs.show
      end,
      function(v) setAbsorbsEnabled(v) end)
    cbAbs:SetPoint("TOPLEFT", 12, y)

    -- NEW: Hide overheal (ABOVE Hide all heals)
    local cbHideOverheal = createCheckbox(pgGen, "Hide overheal",
      "When enabled, pure overheal (effective = 0) is hidden. Default OFF (overheal is shown).",
      function() return DB.hideOverheal == true end,
      function(v)
        DB.hideOverheal = not not v
        DB.healer = DB.healer or {}; DB.healer.hideOverheal = DB.hideOverheal
        if DFFT and DFFT.Healer and type(DFFT.Healer.SetHideOverheal)=="function" then
          pcall(DFFT.Healer.SetHideOverheal, DB.hideOverheal)
        end
      end)

    -- Hide all heals (BELOW Hide overheal)
    local cbHideHeals = createCheckbox(pgGen, "Hide all heals",
      "Completely hides healing numbers (including absorbs) from the display.",
      function() return DB.hideHeals == true end,
      function(v)
        DB.hideHeals = not not v
        DB.healer = DB.healer or {}; DB.healer.hideHeals = DB.hideHeals -- mirror
        -- If we hide all heals, also hide absorbs visually (UI toggle state is left intact)
        if DB.hideHeals then setAbsorbsEnabled(false) end
        if DFFT and DFFT.Healer and type(DFFT.Healer.SetHealsHidden)=="function" then
          pcall(DFFT.Healer.SetHealsHidden, DB.hideHeals)
        end
      end)


    -- Show + on heals (right row)
    local cbPlusHeals = createCheckbox(pgGen, "在治疗前显示+号",
      "在治疗数字前添加+号前缀。",
      function() return DB.healPlusEnabled ~= false end,
      function(v)
        DB.healPlusEnabled = not not v
        if DFFT and DFFT.Healer and type(DFFT.Healer.SetPlusEnabled)=="function" then
          pcall(DFFT.Healer.SetPlusEnabled, not not v)
        end
      end)
-- ===== Heals options stacked vertically (3.5 cm to the right) =====
local xoff = cm(5.0)

cbHideOverheal:ClearAllPoints()
cbHideOverheal:SetPoint("TOPLEFT", cbAbs, "TOPRIGHT", xoff, 65)

cbHideHeals:ClearAllPoints()
cbHideHeals:SetPoint("TOPLEFT", cbHideOverheal, "BOTTOMLEFT", 0, -8)

cbPlusHeals:ClearAllPoints()
cbPlusHeals:SetPoint("TOPLEFT", cbHideHeals, "BOTTOMLEFT", 0, -8)


    y = y - 36

    -- Special FX
    local fxHeader = createLabel(pgGen, "特效设置", "title")
    fxHeader:SetPoint("TOPLEFT", 4, y); y = y - 32

    local cbShake = createCheckbox(pgGen, "暴击时屏幕震动",
      "在你的暴击时添加短暂的屏幕震动效果。",
      function() if DB.shakeEnabled == false or DB.enableShake == false then return false end return true end,
      function(v) local on = not not v; DB.enableShake = on; DB.shakeEnabled = on end)
    cbShake:SetPoint("TOPLEFT", 12, y); y = y - 28

    local cbZoom = createCheckbox(pgGen, "缩放动画",
      "在大数字上添加快速缩放效果。",
      function() if DB.enableZoom == false then return false end return true end,
      function(v) DB.enableZoom = not not v end)
    cbZoom:SetPoint("TOPLEFT", 12, y); y = y - 28

    local cbCritShadow = createCheckbox(pgGen, "大/超大暴击阴影",
      "为大/超大暴击数字添加柔和的黑色阴影以提高可读性。",
      function() if DB.bigCritShadowEnabled == nil then return true end return DB.bigCritShadowEnabled end,
      function(v)
        local on = not not v
        DB.bigCritShadowEnabled = on
        if DFFT and type(DFFT.SetBigCritShadowEnabled)=="function" then pcall(DFFT.SetBigCritShadowEnabled, on) end
      end)
    cbCritShadow:SetPoint("TOPLEFT", 12, y); y = y - 34

    local cbUltraFX = createCheckbox(pgGen, "超大暴击爆炸特效", "在大数字时启用粒子爆炸效果",
      function() if DB.UltraFXEnabled == nil then DB.UltraFXEnabled = true end return DB.UltraFXEnabled end,
      function(v) DB.UltraFXEnabled = not not v end)
    cbUltraFX:SetPoint("TOPLEFT", 12, y); y = y - 32

    local cbCritStar = createCheckbox(pgGen, "暴击指示器", "在暴击数字前显示小星星前缀。",
      function() if DB.showCritSymbol == nil then return false end return DB.showCritSymbol end,
      function(v) DB.showCritSymbol = not not v end)
    cbCritStar:SetPoint("TOPLEFT", 12, y); y = y - 32

    -- 技能图标开关（默认隐藏）。与其他选项左对齐。
    local cbSkillIcons = createCheckbox(pgGen, "技能图标",
      "在数字旁边显示法术/技能图标。",
      function() return DB.showIcons == true end,
      function(v) DB.showIcons = not not v end)
    cbSkillIcons:SetPoint("TOPLEFT", 12, y); y = y - 28

    -- 中文单位缩写选项
    local cbChineseUnits = createCheckbox(pgGen, "使用中文单位缩写", "启用后将K、M、B改为万、亿等中文单位。",
      function() return DB.useChineseUnits and true or false end,
      function(v) DB.useChineseUnits = not not v end)
    cbChineseUnits:SetPoint("TOPLEFT", 12, y); y = y - 28


    -- ====== FONT (via LSM) ======
    local ddLabel = createLabel(pgGen, "字体:", nil)
    ddLabel:SetPoint("TOPLEFT", 460, -8)

    local function getFontNames()
      return (DFFT.ListAvailableFonts and DFFT.ListAvailableFonts())
          or (DFFT.ListFonts and DFFT.ListFonts())
          or {}
    end
    local names = getFontNames()

    local dd = dropdown(pgGen, 220, names,
      function() return DB.fontName end,
      function(name)
        DB.fontName = name
        if type(DFFT.refreshActiveFonts)=="function" then pcall(DFFT.refreshActiveFonts) end
      end)
    dd:SetPoint("TOPLEFT", 520, -18)

    function DFFT.OnMediaFontRegistered()
      names = getFontNames()
      if dd and dd._refresh then dd:_refresh() end
      if type(DFFT.refreshActiveFonts)=="function" then pcall(DFFT.refreshActiveFonts) end
    end

    local cbHideFCT = createCheckbox(pgGen, "隐藏暴雪战斗文字",
      "隐藏暴雪的浮动战斗文字以避免重复数字。",
      function() if DB.hideDefaultCombatText == nil then return true end return DB.hideDefaultCombatText end,
      function(v) DB.hideDefaultCombatText = not not v; if DFFT.ApplyDefaultCombatTextSetting then DFFT.ApplyDefaultCombatTextSetting() end end)
    cbHideFCT:SetPoint("TOPLEFT", 460, -52)

    local cbHideMM = createCheckbox(pgGen, "隐藏小地图图标", "隐藏插件的小地图图标。",
      function() if not DFFT or not DFFT.DB or not DFFT.DB.minimap then return false end return DFFT.DB.minimap.hide end,
      function(v) if DFFT and DFFT.SetMinimapHidden then DFFT.SetMinimapHidden(v) end end)
    cbHideMM:SetPoint("TOPLEFT", cbHideFCT, "BOTTOMLEFT", 0, -10)

    -- Text strata dropdown
    local strataLabel = createLabel(pgGen, "文本框架层级:", nil)
    strataLabel:SetPoint("TOPLEFT", cbHideMM, "BOTTOMLEFT", 0, -12)

    local STRATA_ITEMS = {
      "BACKGROUND","LOW","MEDIUM","HIGH","DIALOG","FULLSCREEN","FULLSCREEN_DIALOG","TOOLTIP"
    }
    local strataDD = dropdown(pgGen, 220, STRATA_ITEMS,
      function() return DB.textStrata or "HIGH" end,
      function(v) DB.textStrata = v end)
    strataDD:SetPoint("TOPLEFT", strataLabel, "BOTTOMLEFT", -16, -6)

    local resetAllBtn = CreateFrame("Button", nil, pgGen, "UIPanelButtonTemplate")
    resetAllBtn:SetSize(220, 24)
    resetAllBtn:SetText("恢复默认设置")
    resetAllBtn:SetPoint("TOPLEFT", strataDD, "BOTTOMLEFT", 16, -14)
    resetAllBtn:SetScript("OnClick", function()
      if DFFT and DFFT.ResetAll then
        DFFT.ResetAll()
      else
        if DFFT and DFFT.defaults then for k,v in pairs(DFFT.defaults) do DB[k] = deepcopy(v) end end
      end
      DB.textSizePercent = 100
      DB.minimap = DB.minimap or {}; DB.minimap.hide = false
      if DFFT and DFFT.SetMinimapHidden then DFFT.SetMinimapHidden(false) end
      if DFFT and DFFT.MinimapButton_RefreshVisibility then DFFT.MinimapButton_RefreshVisibility() end
      DB.healer = DB.healer or {}; DB.healer.absorbs = DB.healer.absorbs or {}
      DB.healer.absorbs.show = false; DB.healer.showAbsorbs = false; DB.showAbsorbs = false; DB.healer.absorbsEnabled = false
      DB.healPlusEnabled = false
      DB.hideHeals = false
      DB.healer.hideHeals = false -- mirror
      DB.hideOverheal = false
      DB.healer.hideOverheal = false -- mirror
      DB.showIcons = true
      DB.textStrata = "HIGH"

      DB.bigCritShadowEnabled = true
      if DFFT and type(DFFT.SetBigCritShadowEnabled)=="function" then pcall(DFFT.SetBigCritShadowEnabled, true) end

      if DFFT and DFFT.defaults and DFFT.defaults.fontName then DB.fontName = DFFT.defaults.fontName end
      if DFFT and type(DFFT.refreshActiveFonts)=="function" then pcall(DFFT.refreshActiveFonts) end
      if type(DFFT.OnMediaFontRegistered)=="function" then pcall(DFFT.OnMediaFontRegistered) end
      if DFFT and DFFT.Healer and type(DFFT.Healer.SetAbsorbsEnabled)=="function" then pcall(DFFT.Healer.SetAbsorbsEnabled, true) end
      if DFFT and DFFT.Healer and type(DFFT.Healer.SetHealsHidden)=="function" then pcall(DFFT.Healer.SetHealsHidden, false) end
      if DFFT and DFFT.Healer and type(DFFT.Healer.SetHideOverheal)=="function" then pcall(DFFT.Healer.SetHideOverheal, false) end

      -- rebuild UI
      if optionsFrame then
        local children = { optionsFrame:GetChildren() }
        for _, c in ipairs(children) do c:Hide(); c:SetParent(nil) end
        tabs, pages = {}, {}
        _populateOptionsFrame(optionsFrame)
        optionsFrame:Show()
      end
      print("|cffffd100DFFT:|r Defaults restored.")
    end)
  end

  -- ===== Colors (two columns) =====
  do
    local frame = pgCol
    local startY = -8
    local colGapX = 460
    local yL, yR = startY, startY

    local SLIDER_TOP_MARGIN = 16
    local AFTER_THEME_GAP   = 48
    local THEME_DD_WIDTH    = 174

    local hdr = createLabel(frame, "Colors", "title")
    hdr:SetPoint("TOPLEFT", 6, yL); yL = yL - 30; yR = yR - 30

    -- ---------- LEFT COLUMN : DAMAGE ----------
    local dmgHeader = createLabel(frame, "Damage", "title")
    dmgHeader:SetPoint("TOPLEFT", 6, yL); yL = yL - 28

    local lblDmgTh = createLabel(frame, "Theme")
    lblDmgTh:SetPoint("TOPLEFT", 12, yL)

    local dmgKeys, seen = {}, {}
    local function pushUnique(displayName) if not seen[displayName] then table.insert(dmgKeys, displayName); seen[displayName]=true end end
    if DFFT.DamageThemes then
      for k,_ in pairs(DFFT.DamageThemes) do
        local disp = (k=="Blizzard UI") and "Basic" or (k=="Monochrome+Or") and "Golden" or k
        pushUnique(disp)
      end
    else
      for k,_ in pairs(LOCAL_DAMAGE_THEMES) do pushUnique(k) end
    end
    table.sort(dmgKeys)

    local function damageThemeDisplay()
      local cur = DB.damageTheme
      if not cur or cur == "" then cur = "Basic" end
      if cur == "Blizzard UI" then cur = "Basic" end
      if cur == "Monochrome+Or" then cur = "Golden" end
      return cur
    end

    local dmgDD = dropdown(frame, THEME_DD_WIDTH, dmgKeys,
      damageThemeDisplay,
      function(v)
        DB.damageTheme = v
        ApplyDamageTheme(v)
        if frame._sqSmall then frame._sqSmall:Refresh() end
        if frame._sqMed   then frame._sqMed:Refresh() end
        if frame._sqBig   then frame._sqBig:Refresh() end
        if frame._sqUltra then frame._sqUltra:Refresh() end
        if frame._sqNT    then frame._sqNT:Refresh() end
      end)
    dmgDD:ClearAllPoints()
    dmgDD:SetPoint("LEFT", lblDmgTh, "RIGHT", 8, 0)
    dmgDD:SetPoint("TOP", lblDmgTh, "TOP", 0, -6)
    frame._dmgDD = dmgDD

    yL = yL - AFTER_THEME_GAP

    local function mkColorRow(title, getTbl, setTbl, suggestions)
      local t = createLabel(frame, title); t:SetPoint("TOPLEFT", 12, yL)
      local sq = CreateFrame("Button", nil, frame, "BackdropTemplate")
      sq:SetSize(38,38)
      sq:SetBackdrop({ bgFile="Interface/Buttons/WHITE8x8", edgeFile="Interface/Tooltips/UI-Tooltip-Border",
                       edgeSize=12, insets={left=2,right=2,top=2,bottom=2} })
      sq:SetPoint("LEFT", t, "RIGHT", 10, 0)
      local function getRGB() local c = getTbl() or {1,1,1}; return c[1],c[2],c[3] end
      local function setRGB(r,g,b) setTbl({r,g,b}) end
      sq.Refresh = function() local r,g,b = getRGB(); sq:SetBackdropColor(r,g,b,1) end
      sq:HookScript("OnShow", sq.Refresh)
      sq:SetScript("OnClick", function()
        local r,g,b = getRGB()
        ShowColorPickerCompat(r,g,b,
          function(nr,ng,nb) setRGB(nr,ng,nb); sq:Refresh(); NotifyConfigChanged() end,
          function(pr,pg,pb) setRGB(pr,pg,pb); sq:Refresh(); NotifyConfigChanged() end)
      end)
      local last = sq
      for _,col in ipairs(suggestions or {}) do
        local c = type(col)=="table" and col or hex(col)
        local sw = CreateFrame("Button", nil, frame, "BackdropTemplate")
        sw:SetSize(22,22)
        sw:SetBackdrop({ bgFile="Interface/Buttons/WHITE8x8", edgeFile="Interface/Tooltips/UI-Tooltip-Border", edgeSize=10 })
        sw:SetBackdropColor(rgb(c)); sw:SetBackdropBorderColor(0,0,0,0.85)
        sw:SetPoint("LEFT", last, "RIGHT", 6, 0)
        sw:SetScript("OnClick", function() setTbl({c[1],c[2],c[3]}); sq:Refresh(); NotifyConfigChanged() end)
        last = sw
      end
      return sq
    end

    frame._sqSmall = mkColorRow("Small Damage",
      function() return DB.smallColor or {1,1,1} end,
      function(v) DB.smallColor=v end,
      { "#F4E8CB", "#c5c5c5", "#acd5d2", "#d2ecbe", "#f3cbda", "#ecb9b8", "#aedcd8", "#b9a3ff" })
    local sSmall = createSlider(frame, "Opacity (Small)", 0.30, 1.00, 0.01,
      function() return DB.smallAlpha or DEFAULT_SMALL_ALPHA end,
      function(v) DB.smallAlpha = v end,
      function(v) return string.format("%.2f", v) end, 240)
    frame._slSmall = sSmall
    yL = yL - SLIDER_TOP_MARGIN
    sSmall:SetPoint("TOPLEFT", 12, yL - 12); yL = yL - 62

    local baseM = DB.smallColor or {1,1,1}
    local medS = {
      shade(baseM, 0.70),
      saturateTowards(shade(baseM, 0.75), {1.00,0.85,0.00}, 0.15),
      saturateTowards(shade(baseM, 0.70), {0.30,0.75,1.00}, 0.10),
      shade(hex("#c5c5c5"), 0.70),
      hex("#b7d6d3"), hex("#b8e6a6"), hex("#f0b7cf"), hex("#8fb0ff"),
    }
    frame._sqMed = mkColorRow("Medium Damage",
      function() return DB.mediumColor or {1,1,1} end,
      function(v) DB.mediumColor=v end, medS)
    local sMed = createSlider(frame, "Opacity (Medium)", 0.30, 1.00, 0.01,
      function() return DB.mediumAlpha or DEFAULT_MEDIUM_ALPHA end,
      function(v) DB.mediumAlpha = v end,
      function(v) return string.format("%.2f", v) end, 240)
    frame._slMed = sMed
    yL = yL - SLIDER_TOP_MARGIN
    sMed:SetPoint("TOPLEFT", 12, yL - 12); yL = yL - 62

    local baseB = DB.mediumColor or {1,1,1}
    local warm = {1.00,0.55,0.00}
    local bigS = {
      saturateTowards(shade(baseB, 0.85), warm, 0.30),
      saturateTowards(shade(baseB, 0.75), warm, 0.40),
      saturateTowards(shade(baseB, 0.80), {1.0,0.15,0.05}, 0.25),
      hex("#ff7b2b"), hex("#ff3a0a"), hex("#cf2626"),
      hex("#2f7fff"), hex("#27e3c5"),
    }
    frame._sqBig = mkColorRow("Big Damage",
      function() return DB.bigColor or {1,1,1} end,
      function(v) DB.bigColor=v end, bigS)
    local sBig = createSlider(frame, "Opacity (Big)", 0.30, 1.00, 0.01,
      function() return DB.bigAlpha or DEFAULT_BIG_ALPHA end,
      function(v) SetBigAlpha(v) end,
      function(v) return string.format("%.2f", v) end, 240)
    frame._slBig = sBig
    yL = yL - SLIDER_TOP_MARGIN
    sBig:SetPoint("TOPLEFT", 12, yL - 12); yL = yL - 62

    local baseU = DB.bigColor or {1,1,1}
    local ultraS = {
      saturateTowards(shade(baseU, 0.90), {1.0,0.15,0.05}, 0.35),
      saturateTowards(shade(baseU, 0.80), {1.0,0.55,0.00}, 0.35),
      hex("#ff3a0a"), hex("#8f0000"),
      hex("#124fd8"), hex("#14c5b5"),
      hex("#ffbf47"), hex("#33b104"),
    }
    frame._sqUltra = mkColorRow("Ultra Damage",
      function() return DB.veryBigColor or {1,1,1} end,
      function(v) DB.veryBigColor=v end, ultraS)
    local sUltra = createSlider(frame, "Opacity (Ultra)", 0.30, 1.00, 0.01,
      function() return DB.veryBigAlpha or DEFAULT_ULTRA_ALPHA end,
      function(v) SetUltraAlpha(v) end,
      function(v) return string.format("%.2f", v) end, 240)
    frame._slUltra = sUltra
    yL = yL - SLIDER_TOP_MARGIN
    sUltra:SetPoint("TOPLEFT", 12, yL - 12); yL = yL - 62

    local ntS = {
      hex("#8ECDF2"), hex("#63B4FF"), hex("#2A8FFF"),
      hex("#CDA98B"), hex("#D4D4D4"), hex("#BFE6CF"),
      hex("#F0E68C"), hex("#C3B6FF"),
    }
    frame._sqNT = mkColorRow("Other targets (non-target)",
      function() return DB.nonTargetColor or {0.45,0.75,1.00} end,
      function(v) DB.nonTargetColor=v end, ntS)
    local sNT = createSlider(frame, "Opacity (Other targets cap)", 0.30, 1.00, 0.01,
      function() return DB.nonTargetAlpha or DEFAULT_NONTARGET_CAP end,
      function(v) SetNonTargetAlpha(v) end,
      function(v) return string.format("%.2f", v) end, 240)
    frame._slNT = sNT
    yL = yL - SLIDER_TOP_MARGIN
    sNT:SetPoint("TOPLEFT", 12, yL - 12); yL = yL - 62

    -- ---------- RIGHT COLUMN : HEALS ----------
    local rightX = colGapX
    local healHeader = createLabel(frame, "Heals", "title")
    healHeader:SetPoint("TOPLEFT", rightX, startY - 30)
    local yRcur = startY - 58

    local lblHealTh = createLabel(frame, "Theme")
    lblHealTh:SetPoint("TOPLEFT", rightX + 2, yRcur)

    local healKeys = {}
    if DFFT.HealThemes then for k,_ in pairs(DFFT.HealThemes) do table.insert(healKeys, k) end
    else for k,_ in pairs(LOCAL_HEAL_THEMES) do table.insert(healKeys, k) end end
    table.sort(healKeys)

    local preview = { }
    local function mkSwatch(i, xoff, labelText)
      if preview[i] then return preview[i] end
      local btn = CreateFrame("Button", nil, frame, "BackdropTemplate")
      btn:SetSize(22,22)
      btn:SetBackdrop({ bgFile="Interface/Buttons/WHITE8x8", edgeFile="Interface/Tooltips/UI-Tooltip-Border", edgeSize=10 })
      btn.txt = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
      btn.txt:SetPoint("TOP", btn, "BOTTOM", 0, -2)
      btn:SetPoint("TOPLEFT", rightX + xoff, yRcur - 38)
      btn.txt:SetText(labelText or "")
      preview[i] = btn
      return btn
    end

    local function shadeTbl(c, m) return {clamp(c[1]*m,0,1), clamp(c[2]*m,0,1), clamp(c[3]*m,0,1)} end
    local function writeTargetFromBase(c)
      DB.healer = DB.healer or {}; DB.healer.colors = DB.healer.colors or {}
      DB.healer.colors.target = DB.healer.colors.target or {}
      DB.healer.colors.target.small  = shadeTbl(c, 1.10)
      DB.healer.colors.target.medium = shadeTbl(c, 1.00)
      DB.healer.colors.target.big    = shadeTbl(c, 0.90)
      DB.healer.colors.target.vbig   = shadeTbl(c, 0.80)
      DB.healer.healSmallColor     = deepcopy(DB.healer.colors.target.small)
      DB.healer.healMediumColor    = deepcopy(DB.healer.colors.target.medium)
      DB.healer.healBigColor       = deepcopy(DB.healer.colors.target.big)
      DB.healer.healVeryBigColor   = deepcopy(DB.healer.colors.target.vbig)
    end
    local function writeNTFromBase(c)
      DB.healer = DB.healer or {}; DB.healer.colors = DB.healer.colors or {}
      DB.healer.colors.nontarget = DB.healer.colors.nontarget or {}
      DB.healer.colors.nontarget.small  = shadeTbl(c, 1.10)
      DB.healer.colors.nontarget.medium = shadeTbl(c, 1.00)
      DB.healer.colors.nontarget.big    = shadeTbl(c, 0.90)
      DB.healer.colors.nontarget.vbig   = shadeTbl(c, 0.80)
      DB.healer.nonTargetHealSmallColor   = deepcopy(DB.healer.colors.nontarget.small)
      DB.healer.nonTargetHealMediumColor  = deepcopy(DB.healer.colors.nontarget.medium)
      DB.healer.nonTargetHealBigColor     = deepcopy(DB.healer.colors.nontarget.big)
      DB.healer.nonTargetHealVeryBigColor = deepcopy(DB.healer.colors.nontarget.vbig)
    end

    local function refreshHealerPreviewFromDB()
      local tgt = (DB.healer and DB.healer.colors and DB.healer.colors.target) or {}
      local nt  = (DB.healer and DB.healer.colors and DB.healer.colors.nontarget) or {}
      local abs = (DB.healer and DB.healer.colors and DB.healer.colors.absorb) or (DB.healer and DB.healer.absorbColor) or {1,1,0.7}
      local hc  = tgt.big or tgt.medium or {0.6,1.0,0.7}
      local nc  = nt.big  or nt.medium  or {0.45,0.85,1.0}
      local ac  = abs or {1.0,0.9,0.5}

      local h1 = mkSwatch(1,  2, "Heal");         local h2 = mkSwatch(2, 30)
      local n1 = mkSwatch(3, 70, "Non-target");   local n2 = mkSwatch(4, 98)
      local a1 = mkSwatch(5,128, "Absorb");       local a2 = mkSwatch(6,156)

      local function sh(c) return {clamp(c[1]*0.85,0,1), clamp(c[2]*0.85,0,1), clamp(c[3]*0.85,0,1)} end
      h1:SetBackdropColor(hc[1],hc[2],hc[3],1);   do local s=sh(hc); h2:SetBackdropColor(s[1],s[2],s[3],1) end
      n1:SetBackdropColor(nc[1],nc[2],nc[3],1);   do local s=sh(nc); n2:SetBackdropColor(s[1],s[2],s[3],1) end
      a1:SetBackdropColor(ac[1],ac[2],ac[3],1);   do local s=sh(ac); a2:SetBackdropColor(s[1],s[2],s[3],1) end

      h1:SetScript("OnClick", function()
        ShowColorPickerCompat(hc[1],hc[2],hc[3],
          function(r,g,b) writeTargetFromBase({r,g,b}); refreshHealerPreviewFromDB(); NotifyConfigChanged() end,
          function(r,g,b) writeTargetFromBase({r,g,b}); refreshHealerPreviewFromDB(); NotifyConfigChanged() end)
      end)
      n1:SetScript("OnClick", function()
        ShowColorPickerCompat(nc[1],nc[2],nc[3],
          function(r,g,b) writeNTFromBase({r,g,b}); refreshHealerPreviewFromDB(); NotifyConfigChanged() end,
          function(r,g,b) writeNTFromBase({r,g,b}); refreshHealerPreviewFromDB(); NotifyConfigChanged() end)
      end)
      a1:SetScript("OnClick", function()
        ShowColorPickerCompat(ac[1],ac[2],ac[3],
          function(r,g,b)
            DB.healer = DB.healer or {}; DB.healer.colors = DB.healer.colors or {}
            DB.healer.colors.absorb = {r,g,b}
            DB.healer.absorbColor   = {r,g,b}
            DB.healer.absorbs       = DB.healer.absorbs or {}
            DB.healer.absorbs.color = {r,g,b}
            refreshHealerPreviewFromDB(); NotifyConfigChanged()
          end,
          function(r,g,b)
            DB.healer = DB.healer or {}; DB.healer.colors = DB.healer.colors or {}
            DB.healer.colors.absorb = {r,g,b}
            DB.healer.absorbColor   = {r,g,b}
            DB.healer.absorbs       = DB.healer.absorbs or {}
            DB.healer.absorbs.color = {r,g,b}
            refreshHealerPreviewFromDB(); NotifyConfigChanged()
          end)
      end)
    end

    local healDD = dropdown(frame, THEME_DD_WIDTH, healKeys,
      function() return DB.healTheme or "Cleric" end,
      function(v) DB.healTheme = v; ApplyHealTheme(v); refreshHealerPreviewFromDB() end)
    healDD:ClearAllPoints()
    healDD:SetPoint("LEFT", lblHealTh, "RIGHT", 8, 0)
    healDD:SetPoint("TOP",  lblHealTh, "TOP",   0, -6)
    frame._healDD = healDD

    if not DB.damageTheme then ApplyDamageTheme("Basic") end
    if not DB.healTheme   then ApplyHealTheme("Cleric") end
    refreshHealerPreviewFromDB()
    yRcur = yRcur - 82

    local restoreBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    restoreBtn:SetSize(230, 24)
    restoreBtn:SetText("Restore Default Colors")
    restoreBtn:SetPoint("TOPLEFT", colGapX + 70, yRcur)
    restoreBtn:SetScript("OnClick", function()
      ResetColorsToDefaults({
        function()
          if frame._sqSmall then frame._sqSmall:Refresh() end
          if frame._sqMed   then frame._sqMed:Refresh()   end
          if frame._sqBig   then frame._sqBig:Refresh()   end
          if frame._sqUltra then frame._sqUltra:Refresh() end
          if frame._sqNT    then frame._sqNT:Refresh()    end
        end,
        function()
          if frame._dmgDD and frame._dmgDD._refresh then frame._dmgDD:_refresh() end
          if frame._healDD and frame._healDD._refresh then frame._healDD:_refresh() end
          refreshHealerPreviewFromDB()
        end,
        function()
          if frame._slSmall then frame._slSmall:SetValue(DEFAULT_SMALL_ALPHA) end
          if frame._slMed   then frame._slMed:SetValue(DEFAULT_MEDIUM_ALPHA) end
          if frame._slBig   then frame._slBig:SetValue(DEFAULT_BIG_ALPHA) end
          if frame._slUltra then frame._slUltra:SetValue(DEFAULT_ULTRA_ALPHA) end
          if frame._slNT    then frame._slNT:SetValue(DEFAULT_NONTARGET_CAP) end
        end
      })
    end)

    local heightNeeded = math.max(-yL, -yRcur) + 100
    frame:SetHeight(heightNeeded)
  end

  -- ===== Donation =====
  do
    local t = pgI:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    t:SetPoint("TOPLEFT", 4, -8)
    t:SetText("Thanks for using " .. FRIENDLY_NAME .. "!")

    local s1 = pgI:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    s1:SetPoint("TOPLEFT", 4, -40)
    s1:SetText("Contact: pdv.simon@gmail.com")

    local s2 = pgI:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    s2:SetPoint("TOPLEFT", 4, -66)
    s2:SetText("This is my very first addon. If you enjoy it, a small donation would make my day and help me keep improving it:")

    -- PayPal
    local box = CreateFrame("EditBox", nil, pgI, "InputBoxTemplate")
    box:SetPoint("TOPLEFT", 4, -92)
    box:SetSize(300, 24)
    box:SetAutoFocus(false)
    box:SetText("https://paypal.me/SimonPdv")
    box:HighlightText(0,0); box:SetCursorPosition(0)

    local copy = CreateFrame("Button", nil, pgI, "UIPanelButtonTemplate")
    copy:SetPoint("LEFT", box, "RIGHT", 8, 0)
    copy:SetSize(80, 22)
    copy:SetText("Copy")
    copy:SetScript("OnClick", function()
      box:SetFocus(); box:HighlightText()
      print("|cffffd100DFFT:|r link highlighted — press Ctrl+C to copy.")
    end)

    -- NEW: Ko-fi
    local klabel = pgI:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    klabel:SetPoint("TOPLEFT", 4, -124)
    klabel:SetText("Ko-fi:")

    local kofibox = CreateFrame("EditBox", nil, pgI, "InputBoxTemplate")
    kofibox:SetPoint("LEFT", klabel, "RIGHT", 8, 0)
    kofibox:SetSize(300, 24)
    kofibox:SetAutoFocus(false)
    kofibox:SetText("https://ko-fi.com/spadawan")
    kofibox:HighlightText(0,0); kofibox:SetCursorPosition(0)

    local koficopy = CreateFrame("Button", nil, pgI, "UIPanelButtonTemplate")
    koficopy:SetPoint("LEFT", kofibox, "RIGHT", 8, 0)
    koficopy:SetSize(80, 22)
    koficopy:SetText("Copy")
    koficopy:SetScript("OnClick", function()
      kofibox:SetFocus(); kofibox:HighlightText()
      print("|cffffd100DFFT:|r link highlighted — press Ctrl+C to copy.")
    end)
  end

  -- ===== Profiles =====
  do
    local frame = pgProf
    local y = -8

    local function rebuildAllOptionsUI()
      if optionsFrame then
        local children = { optionsFrame:GetChildren() }
        for _, c in ipairs(children) do c:Hide(); c:SetParent(nil) end
        tabs, pages = {}, {}
        _populateOptionsFrame(optionsFrame)
        optionsFrame:Show()
        setActiveTab(4)
      end
    end

    local title = createLabel(frame, "Profiles (account-wide)", "title")
    title:SetPoint("TOPLEFT", 6, y); y = y - 30

    local selLabel = createLabel(frame, "Select profile:")
    selLabel:SetPoint("TOPLEFT", 12, y + 4)

    local dd = CreateFrame("Frame", nil, frame, "UIDropDownMenuTemplate")
    dd:SetPoint("TOPLEFT", selLabel, "BOTTOMLEFT", -16, -8)
    UIDropDownMenu_SetWidth(dd, 220)

    local currentSel = nil
    local function listProfiles()
      if DFFT and DFFT.Profile and DFFT.Profile.List then return DFFT.Profile.List() end
      return {}
    end

    local function refreshDropdown()
      local items = listProfiles()
      local active = (DFFT.Profile and DFFT.Profile.GetActiveForChar and DFFT.Profile.GetActiveForChar()) or nil
      UIDropDownMenu_Initialize(dd, function(self, level)
        for _, name in ipairs(items) do
          local info = UIDropDownMenu_CreateInfo()
          info.text = name
          info.checked = (name == currentSel)
          info.func = function()
            currentSel = name
            UIDropDownMenu_SetSelectedName(dd, name)
          end
          UIDropDownMenu_AddButton(info, level)
        end
      end)
      if #items == 0 then
        currentSel = nil; UIDropDownMenu_SetText(dd, "<no profiles>")
      else
        if active and DFFT.Profile.Exists and DFFT.Profile.Exists(active) then
          currentSel = active
        elseif not currentSel or not (DFFT.Profile.Exists and DFFT.Profile.Exists(currentSel)) then
          currentSel = items[1]
        end
        UIDropDownMenu_SetSelectedName(dd, currentSel); UIDropDownMenu_SetText(dd, currentSel)
      end
    end

    refreshDropdown()

    -- Row 2: Save as
    y = y - 78
    local nameLbl = createLabel(frame, "Save as:")
    nameLbl:SetPoint("TOPLEFT", 12, y)
    local nameBox = CreateFrame("EditBox", nil, frame, "InputBoxTemplate")
    nameBox:SetSize(220, 22)
    nameBox:SetAutoFocus(false)
    nameBox:SetPoint("LEFT", nameLbl, "RIGHT", 8, 0)

    local btnSave = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    btnSave:SetSize(100, 22); btnSave:SetText("Save")
    btnSave:SetPoint("LEFT", nameBox, "RIGHT", 8, 0)
    btnSave:SetScript("OnClick", function()
      local n = nameBox:GetText() or ""
      local ok, err = DFFT.Profile.Save(n)
      if not ok then
        print("|cffff5555DFFT:|r Save failed:", err or "?")
        return
      end
      refreshDropdown()
    end)

    local btnOverwrite = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    btnOverwrite:SetSize(140, 22); btnOverwrite:SetText("Overwrite selected")
    btnOverwrite:SetPoint("LEFT", btnSave, "RIGHT", 6, 0)
    btnOverwrite:SetScript("OnClick", function()
      if not currentSel then print("|cffff5555DFFT:|r No profile selected.") return end
      local ok, err = DFFT.Profile.Save(currentSel)
      if not ok then
        print("|cffff5555DFFT:|r Save failed:", err or "?")
        return
      end
      refreshDropdown()
    end)

    -- Row 3: Apply / Delete
    y = y - 40
    local btnApply = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    btnApply:SetSize(240, 24); btnApply:SetText("Apply (set active & load)")
    btnApply:SetPoint("TOPLEFT", 12, y)

    local btnDelete = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    btnDelete:SetSize(120, 24); btnDelete:SetText("Delete")
    btnDelete:SetPoint("LEFT", btnApply, "RIGHT", 8, 0)

    -- Row 4: active info
    y = y - 40
    local activeLbl
    local curActive = (DFFT.Profile and DFFT.Profile.GetActiveForChar and DFFT.Profile.GetActiveForChar()) or nil
    local activeText = "Active for this character: " .. (curActive and ("|cff88ff88"..curActive) or "|cffff8888<none>")
    local _activeLbl = createLabel(frame, activeText); _activeLbl:SetPoint("TOPLEFT", 12, y)
    activeLbl = _activeLbl

    btnApply:SetScript("OnClick", function()
      if not currentSel then print("|cffff5555DFFT:|r No profile selected.") return end
      local ok, err = DFFT.Profile.SetActiveAndLoadForChar(currentSel)
      if not ok then
        print("|cffff5555DFFT:|r Apply failed:", err or "?")
        return
      end
      activeLbl:SetText("Active for this character: |cff88ff88" .. (currentSel or "<none>"))
      local function rebuildAllOptionsUI()
        if optionsFrame then
          local children = { optionsFrame:GetChildren() }
          for _, c in ipairs(children) do c:Hide(); c:SetParent(nil) end
          tabs, pages = {}, {}
          _populateOptionsFrame(optionsFrame)
          optionsFrame:Show()
          setActiveTab(4)
        end
      end
      rebuildAllOptionsUI()
      print("|cffffd100DFFT:|r Loaded + set active:", currentSel)
    end)

    btnDelete:SetScript("OnClick", function()
      if not currentSel then print("|cffff5555DFFT:|r No profile selected.") return end
      local ok, err = DFFT.Profile.Delete(currentSel)
      if not ok then
        print("|cffff5555DFFT:|r Delete failed:", err or "?")
        return
      end
      currentSel = nil
      refreshDropdown()
      local cur = (DFFT.Profile.GetActiveForChar and DFFT.Profile.GetActiveForChar()) or nil
      activeLbl:SetText("Active for this character: " .. (cur and ("|cff88ff88"..cur) or "|cffff8888<none>"))
    end)

    -- Rename row
    y = y - 36
    local rnLabel = createLabel(frame, "Rename selected to:")
    rnLabel:SetPoint("TOPLEFT", 12, y)
    local rnBox = CreateFrame("EditBox", nil, frame, "InputBoxTemplate")
    rnBox:SetSize(220, 22); rnBox:SetAutoFocus(false)
    rnBox:SetPoint("LEFT", rnLabel, "RIGHT", 8, 0)

    local btnRename = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    btnRename:SetSize(100, 22); btnRename:SetText("Rename")
    btnRename:SetPoint("LEFT", rnBox, "RIGHT", 8, 0)
    btnRename:SetScript("OnClick", function()
      if not currentSel then print("|cffff5555DFFT:|r No profile selected.") return end
      local newN = rnBox:GetText() or ""
      local ok, err = DFFT.Profile.Rename(currentSel, newN)
      if not ok then print("|cffff5555DFFT:|r Rename failed:", err or "?"); return end
      currentSel = newN
      refreshDropdown()
      print("|cffffd100DFFT:|r Profile renamed.")
    end)

    -- Small tip
    y = y - 36
    local hint = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    hint:SetPoint("TOPLEFT", 12, y)
    hint:SetText("Tip: choose a profile then click Apply to make it active for this character and load it immediately.")
  end

  -- select a sane tab after build
  if #tabs > 0 then
    setActiveTab(math.max(1, math.min(activeTab or 1, #tabs)))
  end
end

-- =============== build UI ===============
function DFFT.buildOptions()
  -- One-time 85% downscale safeguard (legacy)
  if not DB._initSize85Applied then
    local function sc(k) if type(DB[k])=="number" then DB[k] = DB[k]*0.85 end end
    sc("smallScale"); sc("mediumScale"); sc("bigScaleBase"); sc("bigScaleMax")
    DB._initSize85Applied = true
  end
  if optionsFrame then return end
  local f = CreateFrame("Frame", "DamageFilterFTOptions", UIParent, "BackdropTemplate")
  optionsFrame = f

  if DFFT and type(DFFT.SetBigCritShadowEnabled)=="function" then
    pcall(DFFT.SetBigCritShadowEnabled, not not DB.bigCritShadowEnabled)
  end

  -- make sure size slider is applied once
  ApplyTextSizePercent(DB.textSizePercent or 100)

  _populateOptionsFrame(f)

  -- Ensure current toggles propagated once on UI build
  if DFFT and DFFT.Healer then
    if type(DFFT.Healer.SetHealsHidden)=="function" then pcall(DFFT.Healer.SetHealsHidden, not not DB.hideHeals) end
    if type(DFFT.Healer.SetHideOverheal)=="function" then pcall(DFFT.Healer.SetHideOverheal, not not DB.hideOverheal) end
  end

  if #tabs > 0 then
    setActiveTab(math.max(1, math.min(activeTab or 1, #tabs)))
  end
  f:HookScript("OnShow", function()
    if #tabs > 0 then
      if C_Timer and C_Timer.After then
        C_Timer.After(0, function() setActiveTab(math.max(1, math.min(activeTab or 1, #tabs))) end)
      else
        setActiveTab(math.max(1, math.min(activeTab or 1, #tabs)))
      end
    end
  end)
end

function DFFT.toggleOptions()
  if not optionsFrame then DFFT.buildOptions() end
  if optionsFrame:IsShown() then optionsFrame:Hide() else optionsFrame:Show() end
end
